﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using System.Data.SqlClient;
using System.IO;
using Microsoft.Deployment.Compression.Zip;
using System.Diagnostics;
using System.Web.Configuration;
using System.Configuration;
using System.DirectoryServices;
using System.Security.AccessControl;

namespace OpenComposite.Setup
{
	public class CustomActions
	{
		private static StringBuilder _log = new StringBuilder();
		[CustomAction]
		public static ActionResult CheckDBConnection(Session session)
		{
			session["DBCONNECTIONOK"] = "0";
			bool useWinAuthentication = ( session["USEWINAUTHENTICATION"] == "1" ? true : false );

			SqlConnectionStringBuilder connstr = new SqlConnectionStringBuilder();
			connstr.DataSource = session["SQLSERVER"];
			connstr.PersistSecurityInfo = !useWinAuthentication;
			connstr.IntegratedSecurity = useWinAuthentication;
			connstr.InitialCatalog = "master";
			if ( !useWinAuthentication ) {
				connstr.UserID = session["SQLUSER"];
				connstr.Password = session["SQLPASSWORD"];
			}
			//log("ConnectionString = " + connstr.ToString());
			// validate db connection
			using ( SqlConnection conn = new SqlConnection(connstr.ToString()) ) {
				try {
					bool createDBFailed = false;
					conn.Open();
					string testToolDBQuery = string.Format("CREATE DATABASE {0}", session["SQLDB"]);
					try {
						SqlCommand cmd = new SqlCommand(testToolDBQuery, conn);
						cmd.ExecuteNonQuery();
						SqlCommand dropCmd = new SqlCommand(string.Format("DROP DATABASE {0}", session["SQLDB"]), conn);
						dropCmd.ExecuteNonQuery();
					} catch {
						session["DBCONNECTIONOK"] = "3";
						log("Repository Database already exists.");
						createDBFailed = true;
					}

					string testTravelDBQuery = string.Format("CREATE DATABASE {0}", session["SQLTRAVELDB"]);
					try {
						SqlCommand cmd = new SqlCommand(testTravelDBQuery, conn);
						cmd.ExecuteNonQuery();
						SqlCommand dropCmd = new SqlCommand(string.Format("DROP DATABASE {0}", session["SQLTRAVELDB"]), conn);
						dropCmd.ExecuteNonQuery();
					} catch {
						if ( session["DBCONNECTIONOK"] == "3" ) {
							session["DBCONNECTIONOK"] = "5";
						} else {
							session["DBCONNECTIONOK"] = "4";
						}
						log("Travel Database already exists.");
						createDBFailed = true;
					}

					conn.Close();
					//session.Log("Check DB Connection Successful.");
					log("Check DB Connection Successful.");
					if ( !createDBFailed ) session["DBCONNECTIONOK"] = "1";
				} catch ( Exception ex ) {
					Debug.WriteLine(ex);
					session.Log(ex.Message);
					session.Message(InstallMessage.Info,
						new Record("Database Connection Failed! Please validate the database settings."));
					session["DBCONNECTIONOK"] = "2";
					log(ex.ToString());
				}
			}
			// enable / disable database components
			SetFeatures(session);

			//log("CheckDBConnection: InstallWSToIIS Value: " + session["INSTALLWSTOIIS"]);
			log("DBCONNECTIONOK=" + session["DBCONNECTIONOK"]);

			session.Close();
			writeLog();
			return ActionResult.Success;
		}
		[CustomAction]
		public static ActionResult CheckDBConnectionTrackingPersistence(Session session)
		{
			session["DBCONNECTIONOKTP"] = "0";
			bool useWinAuthentication = ( session["USEWINAUTHENTICATIONTP"] == "1" ? true : false );

			SqlConnectionStringBuilder connstr = new SqlConnectionStringBuilder();
			connstr.DataSource = session["SQLSERVERTP"];
			connstr.PersistSecurityInfo = !useWinAuthentication;
			connstr.IntegratedSecurity = useWinAuthentication;
			connstr.InitialCatalog = "master";
			if ( !useWinAuthentication ) {
				connstr.UserID = session["SQLUSERTP"];
				connstr.Password = session["SQLPASSWORDTP"];
			}
			//log("ConnectionString = " + connstr.ToString());
			// validate db connection
			using ( SqlConnection conn = new SqlConnection(connstr.ToString()) ) {
				try {
					bool createDBFailed = false;
					conn.Open();
					string testDBQuery = string.Format("CREATE DATABASE {0}", session["SQLDBTP"]);
					try {
						SqlCommand cmd = new SqlCommand(testDBQuery, conn);
						cmd.ExecuteNonQuery();
						SqlCommand dropCmd = new SqlCommand(
							string.Format("DROP DATABASE {0}", session["SQLDBTP"]), conn);
						dropCmd.ExecuteNonQuery();
					} catch {
						session["DBCONNECTIONOKTP"] = "3";
						log("Tracking & Persistence Database already exists.");
						createDBFailed = true;
					}

					conn.Close();
					//session.Log("Check DB Connection Successful.");
					log("Check DB Connection Successful.");
					if ( !createDBFailed ) session["DBCONNECTIONOKTP"] = "1";
				} catch ( Exception ex ) {
					Debug.WriteLine(ex);
					session.Log(ex.Message);
					session.Message(InstallMessage.Info,
						new Record("Database Connection Failed! Please validate the database settings."));
					session["DBCONNECTIONOKTP"] = "2";
					log(ex.ToString());
				}
			}
			log("DBCONNECTIONOKTP=" + session["DBCONNECTIONOKTP"]);

			session.Close();
			writeLog();
			return ActionResult.Success;
		}
		[CustomAction]
		public static ActionResult SetFeatures(Session session)
		{
			bool useWinAuthentication = session["USEWINAUTHENTICATION"] == "1" ? true : false;
			// enable / disable database components
			if ( useWinAuthentication ) {
				session.Features["ServerWin"].RequestState = InstallState.Local;
				session.Features["ServerSA"].RequestState = InstallState.Absent;
				if ( session.Features.Contains("ExampleWin") ) {
					session.Features["ExampleWin"].RequestState = InstallState.Local;
					session.Features["ExampleSA"].RequestState = InstallState.Absent;
				}
			} else {
				session.Features["ServerWin"].RequestState = InstallState.Absent;
				session.Features["ServerSA"].RequestState = InstallState.Local;
				if ( session.Features.Contains("ExampleWin") ) {
					session.Features["ExampleWin"].RequestState = InstallState.Absent;
					session.Features["ExampleSA"].RequestState = InstallState.Local;
				}
			}
			return ActionResult.Success;
		}
		[CustomAction]
		public static ActionResult UnZipExampleFiles(Session session)
		{
			try {
				string pathdocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
				ZipInfo zipinfo = new ZipInfo(Path.Combine(pathdocs, string.Format("{0}\\ExampleFiles.zip", ExamplesFolder)));
				zipinfo.Unpack(Path.Combine(pathdocs, ExamplesFolder));
			} catch ( Exception ex ) {
				log(ex.ToString());
			}
			//log("UnZipExampleFiles: InstallWSToIIS Value: " + session["INSTALLWSTOIIS"]);
			//log("UnZipExampleFiles: SQLSERVER Value: " + session["SQLSERVER"]);

			if ( session != null ) session.Close();
			writeLog();
			return ActionResult.Success;
		}
		[CustomAction]
		public static ActionResult PublishWebServices(Session session)
		{
			try {
				string pathdocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
				string sysDrive = Path.GetPathRoot(Environment.SystemDirectory);
				string pathsource = Path.Combine(Path.Combine(pathdocs, ExamplesFolder), WebServiceFolder);
				string pathtarget = Path.Combine(Path.Combine(sysDrive, @"inetpub\wwwroot"), WebServiceFolder);

				//log("[PublishWebServices] SourcePath = '" + pathsource + "'");
				//log("[PublishWebServices] TargetPath = '" + pathtarget + "'");

				doInstallation(session, Environment.MachineName, pathsource, pathtarget);
			} catch ( Exception ex ) {
				log(ex.ToString());
			}
			if ( session != null ) session.Close();
			writeLog();
			return ActionResult.Success;
		}

		#region Private Helper Properties
		private static string ExamplesFolder
		{
			get
			{
				return "Open Composite";
			}
		}
		private static string WebServiceFolder
		{
			get
			{
				return "ExampleServices";
			}
		}
		#endregion

		#region Private Helper Methods
		#region Logging
		/// <summary>
		/// Logs the specified message to the buffer.
		/// </summary>
		/// <param name="message">The message.</param>
		private static void log(string message)
		{
			try {
				_log.AppendLine(message);
			} catch { }
		}

		/// <summary>
		/// Writes the buffered messages to a file.
		/// </summary>
		private static void writeLog()
		{
			if ( _log == null || _log.Length == 0 ) return;

			string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Open Composite");
			Directory.CreateDirectory(path);

			path = Path.Combine(
				path,
				string.Format("install{0}.log", DateTime.Now.ToString("yyyyMMdd-HHmmssfff")));

			FileStream file = new FileStream(path, FileMode.Create);
			if ( file == null ) return;

			using ( StreamWriter sw = new StreamWriter(file) ) {
				sw.WriteLine(_log.ToString());
			}
			_log = new StringBuilder();
		}
		#endregion
		#endregion

		#region Create WebSites by .net


		private static void doInstallation(Session session,
			string sServerName, string sourcePath, string targetPath)
		{
			string sIISRootSubPath = "/W3SVC"; // /1/Root";

			DirectoryInfo diRoot = new DirectoryInfo(sourcePath);
			DirectoryInfo diTarget = new DirectoryInfo(targetPath);
			DirectoryInfo[] diRootSubs = diRoot.GetDirectories();

			copyDirectory(diRoot.FullName, diTarget.FullName);

			DirectoryEntry site = new DirectoryEntry("IIS://" + Environment.MachineName + sIISRootSubPath);
			bool exit = false;
			foreach ( DirectoryEntry core in site.Children ) {
				if ( core.SchemaClassName == "IIsWebServer" ) {
					foreach ( DirectoryEntry root in core.Children ) {
						if ( root.SchemaClassName == "IIsWebVirtualDir" ) {
							sIISRootSubPath = root.Path;
							exit = true;
							root.Close();
							break;
						}
						root.Close();
					}
				}
				core.Close();
				if ( exit ) break;
			}

			createWebDir(Environment.MachineName, diTarget.Name, diTarget.FullName, ref sIISRootSubPath);

			foreach ( DirectoryInfo diSource in diRootSubs ) {
				string target = Path.Combine(targetPath, diSource.Name);

				createVirtualDir(session, sServerName, diSource.Name, target, sIISRootSubPath);
			}
		}

		private static void createWebDir(string sServer, string sVDirName, string sVDirPath,
			ref string sIISRootSubPath)
		{
			string strSchema;// = "IIsWebVirtualDir";

			DirectoryEntry deRoot = new DirectoryEntry(sIISRootSubPath);
			strSchema = deRoot.SchemaClassName;

			deRoot.RefreshCache();
			try {
				DirectoryEntry de = deRoot.Children.Find(sVDirName, strSchema);
				if ( de != null ) {
					sIISRootSubPath += ( sIISRootSubPath.EndsWith("/") ? "" : "/" ) + sVDirName;
					return;
					// EXIT
				}
			} catch ( Exception ex ) {
				Debug.WriteLine(ex.ToString());
			}
			try {
				DirectoryEntry deNewVDir = null;
				foreach ( DirectoryEntry newvdir in deRoot.Children ) {
					if ( newvdir.Name == sVDirName ) {
						deNewVDir = newvdir;
						break;
					}
				}
				if ( deNewVDir == null ) {
					deNewVDir = deRoot.Children.Add(sVDirName, strSchema);
				} else {
					deNewVDir.Invoke("AppCreate", true);
				}
				if ( deNewVDir.Properties.Contains("Path") ) {
					deNewVDir.Properties["Path"].Insert(0, sVDirPath);
				}
				// Save Changes
				deNewVDir.CommitChanges();
				deRoot.CommitChanges();

				deNewVDir.Close();
				deRoot.Close();
			} catch ( Exception ex ) {
				Debug.WriteLine(ex.ToString());
				log(ex.ToString());
			}
			sIISRootSubPath += ( sIISRootSubPath.EndsWith("/") ? "" : "/" ) + sVDirName;
		}
		private static void createVirtualDir(Session session,
			string sServer, string sVDirName, string sVDirPath, string sIISRootSubPath)
		{
			string strSchema = "IIsWebVirtualDir";

			string[] serviceFiles = Directory.GetFiles(sVDirPath, "*.svc");
			if ( serviceFiles.Length == 0 ) {

				string sNewSubPath = sIISRootSubPath;

				createWebDir(sServer, sVDirName, sVDirPath, ref sNewSubPath);

				string subVDirName;
				string subVDirPath;
				foreach ( string subPath in Directory.GetDirectories(sVDirPath) ) {
					subVDirName = Path.GetFileName(subPath);
					subVDirPath = Path.Combine(sVDirPath, Path.GetFileName(subPath));
					createVirtualDir(session, sServer, subVDirName, subVDirPath, sNewSubPath);
				}
				return;
				// EXIT
			}

			DirectoryEntry deRoot = new DirectoryEntry(sIISRootSubPath);

			deRoot.RefreshCache();
			try {
				DirectoryEntry de = deRoot.Children.Find(sVDirName, strSchema);
				if ( de != null ) {
					de.DeleteTree();
					deRoot.CommitChanges();
				}
			} catch {
			}
			try {
				DirectoryEntry deNewVDir = null;
				foreach ( DirectoryEntry newvdir in deRoot.Children ) {
					if ( newvdir.Name == sVDirName ) {
						deNewVDir = newvdir;
						break;
					}
				}
				if ( deNewVDir == null ) {
					deNewVDir = deRoot.Children.Add(sVDirName, strSchema);
				}
				if ( deNewVDir.Properties.Contains("Path") ) {
					deNewVDir.Properties["Path"].Insert(0, sVDirPath);
				}
				deNewVDir.CommitChanges();
				deRoot.CommitChanges();

				// Create a Application
				if ( strSchema == "IIsWebVirtualDir" ) deNewVDir.Invoke("AppCreate", true);

				// Save Changes
				deNewVDir.CommitChanges();
				deRoot.CommitChanges();
				deNewVDir.Close();
				deRoot.Close();
			} catch ( Exception ex ) {
				log(ex.ToString());
			}

			// change web.config
			try {
				string virtualPath =
					string.Format("/{0}/{1}",
						sIISRootSubPath.Substring(sIISRootSubPath.IndexOf(WebServiceFolder)),
						sVDirName);
				log(string.Format("Virtual Path: {0}", virtualPath));
				Configuration config = WebConfigurationManager.OpenWebConfiguration(virtualPath);
				if ( config != null ) {
					log(string.Format("FileName: {0}", config.FilePath));
				} else {
					log("local variable 'config' equals null");
					return;
				}
				ConnectionStringSettings conn = config.ConnectionStrings.ConnectionStrings["ConnectString"];
				if ( conn != null ) {
					#region Create Connection String
					SqlConnectionStringBuilder connstr = new SqlConnectionStringBuilder();
					if ( session != null ) {
						bool useWinAuthentication = session["USEWINAUTHENTICATION"] == "1" ? true : false;

						connstr.DataSource = session["SQLSERVER"];
						connstr.PersistSecurityInfo = !useWinAuthentication;
						connstr.IntegratedSecurity = useWinAuthentication;
						connstr.InitialCatalog = session["SQLTRAVELDB"];
						if ( !useWinAuthentication ) {
							connstr.UserID = session["SQLUSER"];
							connstr.Password = session["SQLPASSWORD"];
						}
						string sConn = connstr.ConnectionString;
						if ( !useWinAuthentication && !string.IsNullOrEmpty(connstr.Password) ) {
							sConn = sConn.Replace(connstr.Password, new string('*', connstr.Password.Length));
						}
						log(string.Format("Connection: {0}", sConn));
					}
					#endregion
					conn.ConnectionString = connstr.ConnectionString;
				}
				if ( config.AppSettings.Settings["TrackingConnection"] != null ) {
					config.AppSettings.Settings["TrackingConnection"].Value = session["DBCONNTP"];
				}
				if ( config.AppSettings.Settings["PersistenceConnection"] != null ) {
					config.AppSettings.Settings["PersistenceConnection"].Value = session["DBCONNTP"];
				}
				config.Save();
			} catch ( Exception ex ) {
				log(ex.ToString());
			}
		}

		private static void copyDirectory(string sSource, string sTarget)
		{
			Directory.CreateDirectory(sTarget);
			DirectoryInfo diSource = new DirectoryInfo(sSource);
			DirectoryInfo[] diSubs = diSource.GetDirectories();
			foreach ( DirectoryInfo diSub in diSubs ) {
				copyDirectory(diSub.FullName, Path.Combine(sTarget, diSub.Name));
			}
			FileInfo[] fiFiles = diSource.GetFiles();
			foreach ( FileInfo fi in fiFiles ) {
				FileInfo newFile = fi.CopyTo(Path.Combine(sTarget, fi.Name), true);
				FileSecurity fs = fi.GetAccessControl();
				//fs.AddAccessRule(new FileSystemAccessRule(Environment.UserDomainName,
				//                                          FileSystemRights.FullControl,
				//                                          AccessControlType.Allow));
				newFile.SetAccessControl(fs);
				newFile.Attributes = System.IO.FileAttributes.Normal;
			}
		}

		#endregion
	}
}

